#pragma once
#include <fstream>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
namespace ns_contrl
{
    using namespace std;
    using namespace ns_model;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_view;
    using namespace httplib;
    // 提供服务的主机
    class Machine
    {
        friend class LoadBlance;
        friend class Control;

    public:
        Machine()
            : _ip(string()), _port(0), _load(0), _mtx(nullptr)
        {
        }
        Machine(string ip, uint16_t port)
            : _ip(ip), _port(port), _load(0), _mtx(new std::mutex())
        {
        }

        // 提升主机负载
        void IncLoad()
        {
            if (_mtx)
                _mtx->lock();
            ++_load;
            if (_mtx)
                _mtx->unlock();
        }

        // 直接使用参数_load也能获取，这样写是为了统一接口
        uint64_t Load()
        {
            uint64_t load = 0;
            if (_mtx)
                _mtx->lock();
            load = _load;
            if (_mtx)
                _mtx->unlock();
            return _load;
        }

        // 减少主机负载
        void DecLoad()
        {
            if (_mtx)
                _mtx->lock();
            --_load;
            if (_mtx)
                _mtx->unlock();
        }

        // 清零负载  主机离线后需要立刻将负载清零，防止下次主机上线时负载不为0
        // 因为我们离线  并不是将主机从_machines中删除 而是从在线主机变离线主机
        void ResetLoad()
        {
            if (_mtx)
                _mtx->lock();
            _load = 0;
            if (_mtx)
                _mtx->unlock();
        }

        ~Machine()
        {
        }

    private:
        std::string _ip;  // ip
        uint16_t _port;   // port
        uint64_t _load;   // 负载
        std::mutex *_mtx; // 锁
    };

    const std::string service_machine = "./conf/service_machine.conf";
    class LoadBlance
    {
    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            lg(Info, "加载 %s 成果", service_machine.c_str());
        }
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                lg(Fatal, "加载: %s 失败", machine_conf.c_str());
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    lg(Warning, "切分: %s 失败", line.c_str());
                    continue;
                }
                Machine m(tokens[0], stoi(tokens[1]));
                _online.push_back(_machines.size());
                _machines.push_back(m);
            }

            in.close();
            return true;
        }

        bool SmartChoice(int *id, Machine **m)
        {
            // 1.选择好的主机(负载少的主机)并更新负载
            // 2.可能需要离线该主机
            lock_guard<mutex> lck(_mtx);
            // 负载均衡的算法 ——> 轮询 + hash
            int online_num = _online.size();
            if (online_num == 0)
            {
                lg(Fatal, "所有的编译主机全部离线，需要赶紧上线!!!");
                return false;
            }

            // 通过遍历的方式找到所有负载最小的机器
            *id = _online[0];
            *m = &_machines[_online[0]];
            uint64_t min_load = _machines[_online[0]].Load();
            for (int i = 1; i < online_num; i++)
            {
                uint64_t cur_load = _machines[_online[i]].Load();
                if (min_load > cur_load)
                {
                    min_load = cur_load;
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }
            return true;
        }

        // 离线某个 id 主机
        void OfflineMachine(int which_id)
        {
            lock_guard<mutex> lock(_mtx);
            for (auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if (*iter == which_id)
                {
                    // 找到了要离线的主机  需要先给负载清0
                    _machines[which_id].ResetLoad();
                    _offline.push_back(*iter);
                    _online.erase(iter);
                    return;
                }
            }
        }

        // 统一上线主机
        void OnlineMachine()
        {
            lock_guard<mutex> lock(_mtx);
            {
                _online.insert(_online.end(), _offline.begin(), _offline.end());
                _offline.erase(_offline.begin(), _offline.end());
            }
            lg(Info, "所有的主机又上线啦!");
        }

        void showMachines()
        {
            lock_guard<mutex> lock(_mtx);
            std::cout << "当前在线主机列表：";
            for (auto &id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << endl;
            std::cout << "当前离线主机列表：";
            for (auto &id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << endl;
        }

        ~LoadBlance()
        {
        }

    private:
        // 可以给我们提供编译服务所有的主机
        // 主机下标充当id
        std::vector<Machine> _machines;
        // 所有在线的主机id
        std::vector<int> _online;

        // 所有离线的主机id
        std::vector<int> _offline;

        // 保证loadBlance安全的锁
        std::mutex _mtx;
    };

    class Control
    {
    public:
        Control()
        {
        }
        
        void RecoveryMachines()
        {
            _load_blance.OnlineMachine();
        }

        // 根据题目数据构建网页
        bool AllQuestions(string *html)
        {
            vector<Question> questions;
            if (_model.GetAllQuestions(&questions))
            {
                // 给题目编号进行排序
                sort(questions.begin(), questions.end(), [](const Question &q1, const Question q2)
                     { return stoi(q1.number) < stoi(q2.number); });
                // 获取题目信息成功，将所有的题目数据构建成网页
                _view.AllExpandHtml(questions, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }

            return true;
        }
        bool OneQuestion(const string &number, string *html)
        {
            Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功，将题目数据构建成网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取指定题目: " + number + " 失败, 不存在!";
                return false;
            }
            return true;
        }

        // 第一个参数in_json  重要部分如下
        // {
        //      "code" : "#include ....",
        //      "input" : "...",
        // }
        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            // 1. 根据题目编号，拿到题目
            struct Question q;
            _model.GetOneQuestion(number, &q);

            // 2. 对in_json进行反序列化，得到题目code、input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 3. 重新拼接用户代码+测试用例，得到新的代码
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + q.tail;
            // Json::Value既可以接受字符串又可以接受整数
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::StyledWriter writer;
            std::string compile_string = writer.write(compile_value);

            // 4. 选择负载最低的主机(做差错处理)
            // 选择规则： 一直选择，直到主机可用，否则就是全部挂掉
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (!_load_blance.SmartChoice(&id, &m))
                {
                    break;
                }
                lg(Info, "选择主机成功,主机id: %d,详情: %s:%d,当前主机负载:%d", id, m->_ip.c_str(), m->_port,m->_load);
                // 5. 发起http请求得到结果
                Client cli(m->_ip, m->_port);
                m->IncLoad();
                if (auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    // 请求成功
                    // 6. 结果赋值给out_josn
                    if (res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        lg(Info,"请求编译和运行服务成功...");
                        _load_blance.showMachines();
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    // 请求失败
                    lg(ns_log::Error, "当前请求的主机id: %d,详情: %s:%d 可能已经离线", id, m->_ip.c_str(), m->_port);

                    _load_blance.OfflineMachine(id);
                }
            }
        }

        ~Control()
        {
        }

    private:
        Model _model;            // 提供后台数据
        View _view;              // 提供网页渲染功能
        LoadBlance _load_blance; // 核心负载均衡器
    };
}
